﻿namespace MySql.Data.MySqlClient
{
    using System;
    using System.Collections.Generic;
    using System.Threading;

    internal sealed class MySqlPool
    {
        private Queue<Driver> idlePool;
        private List<Driver> inUsePool;
        private object lockObject;
        private uint maxSize;
        private uint minSize;
        private Semaphore poolGate;
        private MySql.Data.MySqlClient.ProcedureCache procedureCache;
        private MySqlConnectionStringBuilder settings;

        public MySqlPool(MySqlConnectionStringBuilder settings)
        {
            this.minSize = settings.MinimumPoolSize;
            this.maxSize = settings.MaximumPoolSize;
            if (this.minSize > this.maxSize)
            {
                this.minSize = this.maxSize;
            }
            this.settings = settings;
            this.inUsePool = new List<Driver>((int) this.maxSize);
            this.idlePool = new Queue<Driver>((int) this.maxSize);
            for (int i = 0; i < this.minSize; i++)
            {
                this.CreateNewPooledConnection();
            }
            this.procedureCache = new MySql.Data.MySqlClient.ProcedureCache((int) settings.ProcedureCacheSize);
            this.poolGate = new Semaphore((int) this.maxSize, (int) this.maxSize);
            this.lockObject = new object();
        }

        private Driver CheckoutConnection()
        {
            Driver item = this.idlePool.Dequeue();
            if (!item.Ping())
            {
                item.Close();
                return null;
            }
            if (this.settings.ConnectionReset)
            {
                item.Reset();
            }
            this.inUsePool.Add(item);
            return item;
        }

        private void CreateNewPooledConnection()
        {
            Driver item = Driver.Create(this.settings);
            this.idlePool.Enqueue(item);
        }

        public Driver GetConnection()
        {
            int millisecondsTimeout = (int) (this.settings.ConnectionTimeout * 0x3e8);
            if (!this.poolGate.WaitOne(millisecondsTimeout, false))
            {
                throw new MySqlException(Resources.TimeoutGettingConnection);
            }
            lock (this.lockObject)
            {
                return this.GetPooledConnection();
            }
        }

        private Driver GetPooledConnection()
        {
            Driver driver;
            do
            {
                if (!this.HasIdleConnections)
                {
                    this.CreateNewPooledConnection();
                }
                driver = this.CheckoutConnection();
            }
            while (driver == null);
            return driver;
        }

        public void ReleaseConnection(Driver driver)
        {
            lock (this.lockObject)
            {
                if (this.inUsePool.Contains(driver) && !this.idlePool.Contains(driver))
                {
                    this.inUsePool.Remove(driver);
                    if (driver.IsTooOld())
                    {
                        driver.Close();
                    }
                    else
                    {
                        this.idlePool.Enqueue(driver);
                    }
                    this.poolGate.Release();
                }
            }
        }

        public void RemoveConnection(Driver driver)
        {
            lock (this.lockObject)
            {
                if (this.inUsePool.Contains(driver))
                {
                    this.inUsePool.Remove(driver);
                    this.poolGate.Release();
                }
            }
        }

        private bool HasIdleConnections
        {
            get
            {
                return (this.idlePool.Count > 0);
            }
        }

        public MySql.Data.MySqlClient.ProcedureCache ProcedureCache
        {
            get
            {
                return this.procedureCache;
            }
        }

        public MySqlConnectionStringBuilder Settings
        {
            get
            {
                return this.settings;
            }
            set
            {
                this.settings = value;
            }
        }
    }
}

